https://github.com/nswbmw/N-blog/

1. 环境变量

环境变量(environment variables)一般是指在操作系统中用来指定操作系统运行环境的一些参数。

简单来讲,环境变量就是传递参数给运行程序的。

env  //显示环境变量

nodejs使用

1
2
3
NODE_ENV=test node app

console.log(process.env.NODE_ENV) //test

debug模块使用

1
2
3
4
5
6
7
8
DEBUG=* node app

-------
//windows用户设置

set DEBUG=*
set NODE_ENV=test
node app

cross-env

Run scripts that set and use environment variables across platforms
1
cross-env NODE_ENV=test node app

2. package.json

https://docs.npmjs.com/files/package.json

语义化版本(semver)即 dependencies、devDependencies 和 peerDependencies 里的如:"co": "^4.6.0"

http://semver.org/lang/zh-CN/

semver 格式:主版本号.次版本号.修订号。版本号递增规则如下:

  • 主版本号:做了不兼容的 API 修改
  • 次版本号:做了向下兼容的功能性新增
  • 修订号:做了向下兼容的 bug 修正

3. npm操作

搜索npm模块

1. npm init

初始化一个空项目

2. npm install

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ npm install -h

npm install (with no args, in package dir)
npm install [<@scope>/]<pkg>
npm install [<@scope>/]<pkg>@<tag>
npm install [<@scope>/]<pkg>@<version>
npm install [<@scope>/]<pkg>@<version range>
npm install <folder>
npm install <tarball file>
npm install <tarball url>
npm install <git:// url>
npm install <github username>/<github project>

aliases: i, isntall, add
common options: [--save-prod|--save-dev|--save-optional] [--save-exact] [--no-save]

直接使用 npm i 安装的模块是不会写入 package.json 的 dependencies (或 devDependencies),需要额外加个参数:

  1. npm i express --save/npm i express -S
    (安装 express,同时将 "express": "^4.14.0" 写入 dependencies )
  1. npm i express --save-dev/npm i express -D
    (安装 express,同时将 "express": "^4.14.0" 写入 devDependencies )
  1. npm i express --save --save-exact
    (安装 express,同时将固定版本号 "express": "4.14.0" 写入 dependencies )
1
npm config set save-exact true

这样每次 npm i xxx --save 的时候会锁定依赖的版本号,相当于加了 --save-exact 参数。(npm config set 命令将配置写到了 ~/.npmrc 文件,运行 npm config list查看。)

3. npm scripts

npm scripts

npm start 等价于npm run start
npm test 等价于 npm run test

4. npm shrinkwrap

npm-shrinkwrap

运行后会在当前目录下产生一个 npm-shrinkwrap.json,里面包含了通过 node_modules 计算出的模块的依赖树及版本。

只要目录下有 npm-shrinkwrap.json 则运行 npm install 的时候会优先使用 npm-shrinkwrap.json 进行安装,没有则使用 package.json 进行安装。

npm shrinkwrap 只会生成 dependencies 的依赖,不会生成 devDependencies 的。

4. 初始化express项目

express官方文档

npm init

npm i express --save

1
2
3
4
5
6
7
8
9
10
11
//index.js
----------

const express = require('express')
const app = express()

app.get('/', function (req, res) {
res.send('hello, express')
})

app.listen(3000)

运行 node index,打开浏览器访问 localhost:3000 时,页面应显示 hello, express

5. node-supervisor

node-supervisor

A little supervisor script for nodejs. It runs your program, and watches for code changes, so you can have hot-code reloading-ish behavior, without worrying about memory leaks and making sure you clean up all the inter-module references, and without a whole new require system.
1
npm i -g supervisor

运行 supervisor index 启动程序, supervisor 会监听当前目录下 node 和 js 后缀的文件,当这些文件发生改动时,supervisor 会自动重启程序。

6. 路由匹配

Path-to-RegExp

Turn a path string such as /user/:name into a regular expression.

express 使用了 path-to-regexp 模块实现的路由匹配。

1
2
3
app.get('/users/:name', function (req, res) {
res.send('hello, ' + req.params.name)
})

req 包含了请求来的相关信息,res 则用来返回该请求的响应。

常用的 req 的属性:

req.query:
解析 url 中的querystring,如 ?name=haha,req.query 的值为 {name: 'haha'}

req.params:
解析 url 中的占位符,如 /:name,访问 /haha,req.params 的值为 {name: 'haha'}

req.body:
解析后请求体,需使用相关的模块,如 body-parser,请求体为 {"name": "haha"},则 req.body 为 {name: 'haha'}

express.Router

express.Router

创建空文件夹 routes,在 routes 目录下创建 index.js 和 users.js。最后代码如下:

index.js

1
2
3
4
5
6
7
8
9
const express = require('express')
const app = express()
const indexRouter = require('./routes/index')
const userRouter = require('./routes/users')

app.use('/', indexRouter)
app.use('/users', userRouter)

app.listen(3000)

routes/index.js

1
2
3
4
5
6
7
8
const express = require('express')
const router = express.Router()

router.get('/', function (req, res) {
res.send('hello, express')
})

module.exports = router

routes/users.js

1
2
3
4
5
6
7
8
const express = require('express')
const router = express.Router()

router.get('/:name', function (req, res) {
res.send('hello, ' + req.params.name)
})

module.exports = router

以上代码的意思是:我们将 //users/:name 的路由分别放到了 routes/index.js 和 routes/users.js 中,每个路由文件通过生成一个 express.Router 实例 router 并导出,通过 app.use 挂载到不同的路径。这两种代码实现了相同的功能,但在实际开发中推荐使用 express.Router 将不同的路由分离到不同的路由文件中。

7. 模板引擎ejs

1
npm i ejs --save

index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
const path = require('path')
const express = require('express')
const app = express()
const indexRouter = require('./routes/index')
const userRouter = require('./routes/users')

app.set('views', path.join(__dirname, 'views'))// 设置存放模板文件的目录
app.set('view engine', 'ejs')// 设置模板引擎为 ejs

app.use('/', indexRouter)
app.use('/users', userRouter)

app.listen(3000)

通过 app.set 设置模板引擎为 ejs 和存放模板的目录。
在 myblog 下新建 views 文件夹,在 views 下新建 users.ejs,添加如下代码:

views/users.ejs

1
2
3
4
5
6
7
8
9
10
11
12
<!DOCTYPE html>
<html>
<head>
<style type="text/css">
body {padding: 50px;font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;}
</style>
</head>
<body>
<h1><%= name.toUpperCase() %></h1>
<p>hello, <%= name %></p>
</body>
</html>

routes/users.js

1
2
3
4
5
6
7
8
9
10
const express = require('express')
const router = express.Router()

router.get('/:name', function (req, res) {
res.render('users', {
name: req.params.name
})
})

module.exports = router

通过调用 res.render 函数渲染 ejs 模板。

res.render 第一个参数是模板的名字,这里是 users 则会匹配 views/users.ejs,第二个参数是传给模板的数据,这里传入 name,则在 ejs 模板中可使用 name

res.render 的作用就是将模板和数据结合生成 html,同时设置响应头中的 Content-Type: text/html,告诉浏览器我返回的是 html,不是纯文本,要按 html 展示。

ejs 3 种常用标签:

<% code %>:运行 JavaScript 代码,不输出
<%= code %>:显示转义后的 HTML内容
<%- code %>:显示原始 HTML 内容

<%= code %><%- code %> 都可以是 JavaScript 表达式生成的字符串。

当变量 code 为普通字符串时,两者没有区别。
当 code 比如为 <h1>hello</h1> 这种字符串时,<%= code %> 会原样输出 <h1>hello</h1>,而 <%- code %> 则会显示 H1 大的 hello 字符串。

ejs#tags

拆分模板组件

  • 模板可复用,减少重复代码
  • 主模板结构清晰

views/header.ejs

1
2
3
4
5
6
7
8
<!DOCTYPE html>
<html>
<head>
<style type="text/css">
body {padding: 50px;font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;}
</style>
</head>
<body>

views/footer.ejs

1
2
  </body>
</html>

views/users.ejs

users.ejs 通过 ejs 内置的 include 方法引入header.ejsfooter.ejs

(要用 <%- include('header') %> 而不是 <%= include('header') %>)

1
2
3
4
<%- include('header') %>
<h1><%= name.toUpperCase() %></h1>
<p>hello, <%= name %></p>
<%- include('footer') %>

8. 中间件

app.use([path,] callback [, callback…])

express 中的中间件(middleware)就是用来处理请求的,当一个中间件处理完,可以通过调用 next() 传递给下一个中间件,如果没有调用 next(),则请求不会往下传递,如内置的 res.render其实就是渲染完 html 直接返回给客户端,没有调用 next(),从而没有传递给下一个中间件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const express = require('express')
const app = express()

app.use(function (req, res, next) {
console.log('1')
next()
})

app.use(function (req, res, next) {
console.log('2')
res.status(200).end()
})

app.listen(3000)

-------------
1
2

通过 app.use 加载中间件,在中间件中通过 next 将请求传递到下一个中间件,next 可接受一个参数接收错误信息,如果使用了 next(error),则会返回错误而不会传递到下一个中间件。

中间件的加载顺序很重要。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const express = require('express')
const app = express()

app.use(function (req, res, next) {
console.log('1')
next(new Error('haha'))
})

app.use(function (req, res, next) {
console.log('2')
res.status(200).end()
})

app.listen(3000)

--------------
1
Error: haha

Express 中间件—-body-parser

错误处理

Error Handling

express 内置了一个默认的错误处理器,假如我们想手动控制返回的错误内容,则需要加载一个自定义错误处理的中间件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//index.js
----------

const express = require('express')
const app = express()

app.use(function (req, res, next) {
console.log('1')
next(new Error('haha'))
})

app.use(function (req, res, next) {
console.log('2')
res.status(200).end()
})

//错误处理
app.use(function (err, req, res, next) {
console.error(err.stack)
res.status(500).send('Something broke!')
})

app.listen(3000)

-----------------
Something broke!

9. 配置文件config-lite

config-lite

A super simple & flexible & intuitive config module, support yaml & toml.

config-lite 是一个轻量的读取配置文件的模块。config-lite 会根据环境变量(NODE_ENV)的不同加载 config 目录下不同的配置文件。如果不设置NODE_ENV,则读取默认的 default 配置文件,如果设置了 NODE_ENV,则会合并指定的配置文件和 default 配置文件作为配置,config-lite 支持 .js、.json、.node、.yml、.yaml后缀的文件(依次降级查找并合并 default 配置)。

例子:如果程序以 NODE_ENV=production node app 启动,则 config-lite 会依次降级查找 config/production.js、config/production.json、config/production.node、config/production.yml、config/production.yaml 并合并 default 配置。

10. 会话

由于 HTTP 协议是无状态的协议,所以服务端需要记录用户的状态时,就需要用某种机制来识别具体的用户,这个机制就是会话(Session)

  • cookie 存储在浏览器(有大小限制),session 存储在服务端(没有大小限制)
  • 通常 session 的实现是基于 cookie 的,session id 存储于 cookie 中 session 更安全,cookie可以直接在浏览器查看甚至编辑

COOKIE和SESSION有什么区别?

  • Session是在服务端保存的一个数据结构,用来跟踪用户的状态,这个数据可以保存在集群、数据库、文件中;
  • Cookie是客户端保存用户信息的一种机制,用来记录用户的一些信息,也是实现Session的一种方式。

express-session 中间件

引入中间件,实现对会话的支持.

1
app.use(session(options))

session 中间件会在 req 上添加 session 对象,即 req.session 初始值为 {},当我们登录后设置 req.session.user = 用户信息,返回浏览器的头信息中会带上 set-cookiesession id 写到浏览器 cookie 中,那么该用户下次请求时,通过带上来的 cookie 中的 session id 我们就可以查找到该用户,并将用户信息保存到 req.session.user

connect-flash中间件

引入中间件,实现页面通知只显示一次,刷新后消失。

1
npm install connect-flash

The flash is a special area of the session used for storing messages. Messages are written to the flash and cleared after being displayed to the user. The flash is typically used in combination with redirects, ensuring that the message is available to the next page that is to be rendered.

它是基于 session 实现的,设置初始值 req.session.flash={},通过 req.flash(name, value) 设置这个对象下的字段和值,通过 req.flash(name) 获取这个对象下的值,同时删除这个字段,实现了只显示一次刷新后消失的功能。

express-session、connect-mongo 和 connect-flash 的区别与联系

express-session: 会话(session)支持中间件
connect-mongo: 将 session 存储于 mongodb,需结合 express-session 使用,我们也可以将 session 存储于 redis,如 connect-redis
connect-flash: 基于 session 实现的用于通知功能的中间件,需结合 express-session 使用

11. objectid-to-timestamp

addCreatedAt 自定义插件(通过 _id 生成时间戳)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
lib/mongo.js
------------

const moment = require('moment')
const objectIdToTimestamp = require('objectid-to-timestamp')

// 根据 id 生成创建时间 created_at
mongolass.plugin('addCreatedAt', {
afterFind: function (results) {
results.forEach(function (item) {
item.created_at = moment(objectIdToTimestamp(item._id)).format('YYYY-MM-DD HH:mm')
})
return results
},
afterFindOne: function (result) {
if (result) {
result.created_at = moment(objectIdToTimestamp(result._id)).format('YYYY-MM-DD HH:mm')
}
return result
}
})

24 位长的 ObjectId 前 4 个字节是精确到秒的时间戳,所以我们没有额外的存创建时间(如: createdAt)的字段。

ObjectId 生成规则

ObjectId 生成规则

ObjectId 生成规则